// package 提供P2P通信能力，流处理逻辑
package p2p

import (
	"context"
	"fmt"
	"osiris/config"
	"osiris/core/conversion"
	"osiris/core/state"
	"osiris/dao"
	"osiris/dto"
	"osiris/logger"
	"osiris/model"
	"osiris/ocrypto/ed25519"
	"osiris/p2p/opubsub"
	"strings"
	"sync"

	"time"

	"github.com/libp2p/go-libp2p"
	//pubsub "github.com/libp2p/go-libp2p-pubsub"
	"github.com/libp2p/go-libp2p/core/crypto"
	"github.com/libp2p/go-libp2p/core/host"
	peer "github.com/libp2p/go-libp2p/core/peer"
	pstore "github.com/libp2p/go-libp2p/core/peerstore"
	ma "github.com/multiformats/go-multiaddr"

	"github.com/libp2p/go-libp2p/p2p/discovery/mdns"
)

// var Host
//
//	@param Ha P2P node，在main中通过InitPeerStore()初始化
//	@param NodeCodeName 节点的代号，在main中由私钥加短hash生成,用于节点数据库、local.bf.gz命名
var (
	ha host.Host
	//pubsubManager *pubsub.PubSubManager
	/* ps            *pubsub.PubSub */
)

// DiscoveryInterval is how often we re-publish our mDNS records.
const DiscoveryInterval = time.Hour

// DiscoveryServiceTag is used in our mDNS advertisements to discover other chat peers.
const DiscoveryServiceTag = "pubsub-chat-example"

// discoveryNotifee gets notified when we find a new peer via mDNS discovery
type discoveryNotifee struct {
	h host.Host
}

// HandlePeerFound connects to peers discovered via mDNS. Once they're connected,
// the PubSub system will automatically start interacting with them if they also
// support PubSub.
func (n *discoveryNotifee) HandlePeerFound(pi peer.AddrInfo) {
	fmt.Printf("discovered new peer %s\n", pi.ID)
	err := n.h.Connect(context.Background(), pi)
	if err != nil {
		fmt.Printf("error connecting to peer %s: %s\n", pi.ID, err)
	}
}

// MakeBasicHost MakeBasicHost use the given ed25519 private key to creates a LibP2P host with specific peer ID listening on the given multiaddress.
//
//	@param httpServerPort
//	@param listenPort
//	@return error
func MakeBasicHost(httpServerPort int, listenPort int) error {
	_, privKey := ed25519.GetPeerKeyPair()
	opts := []libp2p.Option{
		libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", listenPort)),
		libp2p.Identity(privKey),
		//libp2p.DisableRelay(),
	}

	var err error
	ha, err = libp2p.New(opts...) //之前opts...前面有context.Background(),
	if err != nil {
		return err
	} else {
		logger.Info(map[string]interface{}{"[p2p] [Make Basic Host] libp2p.New": "successfully create the node"})
		logger.Println("Successfully create P2P node")
	}

	// Build host multiaddress
	hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", ha.ID().String()))

	// Now we can build a full multiaddress to reach this host
	// by encapsulating both addresses:
	addrs := ha.Addrs()
	var addr ma.Multiaddr
	// select the address starting with "ip4"
	for _, i := range addrs {
		if strings.HasPrefix(i.String(), "/ip4") {
			addr = i
			break
		}
	}

	fullAddr := addr.Encapsulate(hostAddr)
	logger.Printf("Node Full Multiaddr: %s\n", fullAddr)
	logger.Printf("Node Code Name: %s\n", config.NodeCodeName)

	initProtocol([]Protocol{
		PingProtocol{},
		DiscoveryProtocol{},
		TxProtocol{
			MulticastProtocol: MulticastProtocol{
				StaticExcluded: make(map[peer.ID]bool, 0),
				DynamicExcluded: make(map[peer.ID]bool, 0),
			},
		},
		PosProtocol{
			MulticastProtocol: MulticastProtocol{
				StaticExcluded: make(map[peer.ID]bool, 0),
				DynamicExcluded: make(map[peer.ID]bool, 0),
			},
		},
	})

	// create a new PubSub service using the GossipSub router
	/* 	ctx := context.Background()
	   	ps, err := pubsub.NewGossipSub(ctx, ha)
	   	if err != nil {
	   		logger.Error(map[string]interface{}{"[p2p] [Make Basic Host] pubsub.NewGossipSub()": err})
	   		return err
	   	} */

	// setup local mDNS discovery
	/* if err := setupDiscovery(ha); err != nil {
		logger.Error(map[string]interface{}{"[p2p] [Make Basic Host]set up discovery": err})
		panic(err)
	} */
	/* 	pubsubManager = &opubsub.PubSubManager{}
	   	pubsubManager.AddTopicManager(opubsub.TxTopicManager{}).
	   		InitPubSub(ctx, ps) */

	return nil
}

func initProtocol(protocols []Protocol) {
	for _, protocol := range protocols {
		ha.SetStreamHandler(protocol.protocolName(), protocol.handleStream)
	}
}

func EnableBootstrapProtocol() {
	bsProtocol := BootstrapProtocol{}
	ha.SetStreamHandler(bsProtocol.protocolName(), bsProtocol.handleStream)
}

// setupDiscovery creates an mDNS discovery service and attaches it to the libp2p Host.
// This lets us automatically discover peers on the same LAN and connect to them.
func setupDiscovery(h host.Host) error {
	// setup mDNS discovery to find local peers
	s := mdns.NewMdnsService(h, DiscoveryServiceTag, &discoveryNotifee{h: h})
	return s.Start()
}

func OpenStream(protocol Protocol, peerID peer.ID, jsonStr string, wg *sync.WaitGroup) {
	protocol.openStream(peerID, jsonStr, wg)
}

func Publish(topicID opubsub.TopicID, json []byte) {
	//pubsubManager.Publish(topicID, json)
}

func Close() error {
	return ha.Close()
}

// InitPeerStore 读取peers数据表中的所有数据并加入Ha.PeerStore
//
//	@return error 遇到的第一个error
func InitPeerStore() error {
	//清空PeerStore
	//TODO corfim：有没必要？节点下线后会这玩意会自己缓存不？
	allPeers := ha.Peerstore().Peers()
	for _, peerID := range allPeers {
		if peerID == ha.ID() {
			continue // 跳过自己的节点
		}
		ha.Peerstore().ClearAddrs(peerID)
	}

	//从MYSQL中加载所有已存节点的Multiaddr
	peerModels, err := dao.GetAllAddrs()
	if err != nil {
		logger.Error(map[string]interface{}{"[p2p] [Init PeerStore] dao.GetAllPeers": err.Error()})
		return err
	}

	for _, peerModel := range peerModels {
		peerID, err1 := conversion.StrToPeerID(peerModel.PeerIDStr)
		if err1 != nil {
			logger.Error(map[string]interface{}{fmt.Sprintf("[p2p] [Init PeerStore] core.GeneratePeerID from %s ", peerModel.PeerIDStr): err1})
			return err1
		}

		addr, err2 := conversion.StrToMultiaddr(peerModel.MultiaddrStr)
		if err2 != nil {
			logger.Error(map[string]interface{}{fmt.Sprintf("[p2p] [Init PeerStore] core.GenerateMultiaddr from %s ", peerModel.MultiaddrStr): err2})
			return err2
		}
		ha.Peerstore().AddAddr(peerID, addr, pstore.PermanentAddrTTL)
	}

	//从MYSQL中加载所有已存节点的ECC 公钥
	pubKeyModels, err3 := dao.GetAllPubKeys()
	if err3 != nil {
		logger.Error(map[string]interface{}{"[p2p] [Init PeerStore] dao.GetAllPubKeys": err3.Error()})
		return err
	}

	for _, pubkeyModel := range pubKeyModels {
		peerID, err4 := conversion.StrToPeerID(pubkeyModel.PeerIDStr)
		if err4 != nil {
			logger.Error(map[string]interface{}{fmt.Sprintf("[p2p] [Init PeerStore] core.GeneratePeerID from %s ", pubkeyModel.PeerIDStr): err4})
			return err4
		}

		pubKey, err5 := ed25519.StrToPubKey(pubkeyModel.PubKeyStr)
		if err5 != nil {
			logger.Error(map[string]interface{}{fmt.Sprintf("[p2p] [Init PeerStore] ocrypto.StrToPubKey from %s ", pubkeyModel.PubKeyStr): err5})
			return err5
		}
		ha.Peerstore().AddPubKey(peerID, pubKey)
	}

	return nil
}

// InitSelfState 在节点状态树中初始化自身节点
//
//	@return error
func InitSelfState() error {
	peerIDBytes, err := conversion.StrToPeerIDBytes(GetSelfID().String())
	if err != nil {
		logger.Error(map[string]interface{}{"[p2p] [Init Self State] core.StrToPeerIDBytes()": err})
		return err
	}

	//TODO 多链
	return state.AddPeerLeaf(0, peerIDBytes)
}

// StoreSenderPeer (调用前必须验证节点合法性！)将发送方节点的peer.ID-multiaddr、peer.ID-pubKey存入Ha.PeerStore和peers数据表,并将节点加入节点账本
//
//	@param peerDto
//	@return error 出现的第一个错误
func StoreSenderPeer(peerDto dto.PeerDto) error {
	senderPeerIDStr := peerDto.SenderPeerID
	logger.Printf("Store Verified Peer: [PeerID %s]\n", senderPeerIDStr)

	//将节点的公钥保存进Ha.PeerStore
	senderPeerID, err1 := conversion.StrToPeerID(senderPeerIDStr)
	if err1 != nil {
		logger.Error(map[string]interface{}{fmt.Sprintf("[p2p] [Store Sender Peer] generate peer.ID from string %s", senderPeerIDStr): err1})
		return err1
	}

	//将节点加入节点账本
	peerIDBytes, err2 := conversion.StrToPeerIDBytes(senderPeerIDStr)
	if err2 != nil {
		logger.Error(map[string]interface{}{fmt.Sprintf("[p2p] [Store Sender Peer]  peer.ID str %s to bytes", senderPeerIDStr): err2})
		return err2
	}
	//TODO 多通道
	state.AddPeerLeaf(0, peerIDBytes)

	StorePeerPubKey(senderPeerID, peerDto.SenderPubKey)

	//将节点的所有Multiaddr保存进Ha.PeerStore
	for _, addrStr := range peerDto.SenderMultiaddrs {

		addr, err1 := conversion.StrToMultiaddr(addrStr)
		if err1 != nil {
			return err1
		}

		err2 := StorePeerMultiaddr(senderPeerID, addr)
		if err2 != nil {
			return err2
		}
	}

	return nil
}

func StorePeerMultiaddr(peerID peer.ID, addr ma.Multiaddr) error {
	logger.Info(map[string]interface{}{"[p2p] [Store Peer Multiaddr]": fmt.Sprintf("[PeerID %s, Multiaddr %s]", peerID.String(), addr.String())})
	ha.Peerstore().AddAddr(peerID, addr, pstore.PermanentAddrTTL)
	return dao.AddAddrByModel(model.AddrModel{
		PeerIDStr:    peerID.String(),
		MultiaddrStr: addr.String(),
	})
}

func StorePeerPubKey(peerID peer.ID, pubKeyStr string) error {
	pubKey, err := ed25519.StrToPubKey(pubKeyStr)
	if err != nil {
		logger.Error(map[string]interface{}{"[p2p] [Store Peer PubKey] str to pubKey": err})
		return err
	}

	logger.Info(map[string]interface{}{"[p2p] [Store Peer PubKey]": fmt.Sprintf("[PeerID %s, PubKey %s]", peerID.String(), pubKeyStr)})
	ha.Peerstore().AddPubKey(peerID, pubKey)
	return dao.AddPubKeyByModel(model.PubKeyModel{
		PeerIDStr: peerID.String(),
		PubKeyStr: pubKeyStr,
	})
}

func GenerateTxDto(txData dto.TxData) dto.TxDto {
	txDto := dto.TxDto{
		SenderPeerID:     GetSelfID().String(),
		SenderPubKey:     ed25519.GetPeerPubKeyStr(),
		SenderMultiaddrs: GetSelfMultiaddrStrs(),
		TxData:           txData,
	}

	txDto.HashAndSign()
	return txDto
}

func GetSelfMultiaddrStrs() []string {
	return conversion.MultiaddrsToStrings(ha.Addrs())
}

func GetSelfID() peer.ID {
	return ha.ID()
}

func GetPeerIDs() peer.IDSlice {
	return ha.Peerstore().Peers()
}

func GetAddrs(peerID peer.ID) []ma.Multiaddr {
	return ha.Peerstore().Addrs(peerID)
}

func GetPubKey(peerID peer.ID) crypto.PubKey {
	return ha.Peerstore().PubKey(peerID)
}

func GetAddrMap() map[string][]string {
	peerIDs := GetPeerIDs()
	addrMap := make(map[string][]string, 0)
	for _, peerID := range peerIDs {
		addrs := GetAddrs(peerID)
		addrMap[peerID.String()] = conversion.MultiaddrsToStrings(addrs)
	}
	return addrMap
}

func AddAddr(peerID peer.ID, addr ma.Multiaddr) {
	ha.Peerstore().AddAddr(peerID, addr, pstore.PermanentAddrTTL)
}
